﻿namespace MicroCloud.Entity
{
    #region "平滑权重从数据库选择器"
    /// <summary>
    /// 平滑权重从数据库选择器
    /// </summary>
    public sealed class WeightSlaveDatabaseSelector : ISlaveDatabaseSelector
    {
        //字段
        private static readonly object LockObj = new();
        private readonly ILogger _logger;
        private Queue<int> _queue = new();

        #region "属性"
        #region "获取 名称"
        /// <summary>
        /// 获取 名称
        /// </summary>
        public string Name => "Weight";
        #endregion
        #region "获取 从数据库选择策略"
        /// <summary>
        /// 获取 从数据库选择策略
        /// </summary>
        public SlaveDatabaseSelectorType SelectorType => SlaveDatabaseSelectorType.Weight;
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个 <see cref="WeightSlaveDatabaseSelector"/> 类型的新实例"
        /// <summary>
        /// 初始化一个 <see cref="WeightSlaveDatabaseSelector"/> 类型的新实例
        /// </summary>
        /// <param name="provider"></param>
        public WeightSlaveDatabaseSelector(IServiceProvider provider)
        {
            _logger = provider.GetLogger(GetType());
        }
        #endregion

        #endregion

        #region "方法"
        #region "从所有从数据库中返回一个"
        /// <summary>
        /// 从所有从数据库中返回一个
        /// </summary>
        /// <param name="slaves">所有从数据库</param>
        /// <returns></returns>
        public SlaveDatabaseInfo Select(SlaveDatabaseInfo[] slaves)
        {
            lock (LockObj)
            {
                if (_queue.Count == 0)
                {
                    _queue = GetIndexes(slaves);
                }

                int index = _queue.Dequeue();
                SlaveDatabaseInfo slave = slaves[index];
                _logger.LogDebug("平滑权重选取了 {name} 从数据库，权重：{weight}。", slave.Name, slave.Weight);
                return slave;
            }
        }
        #endregion

        #endregion

        #region "静态私有方法"
        #region "获取索引"
        /// <summary>
        /// 获取索引
        /// </summary>
        /// <param name="slaves"></param>
        /// <returns></returns>
        private static Queue<int> GetIndexes(SlaveDatabaseInfo[] slaves)
        {
            SlaveDatabaseOptionsWrap[] wraps = slaves.Select(m => new SlaveDatabaseOptionsWrap(m)).ToArray();
            int sum = wraps.Sum(m => m.Weight);
            Queue<int> queue = new();
            for (int i = 0; i < sum; i++)
            {
                int index = NextIndex(wraps);
                queue.Enqueue(index);
            }

            return queue;
        }
        #endregion
        #region "下一个索引"
        /// <summary>
        /// 下一个索引
        /// </summary>
        /// <param name="wraps"></param>
        /// <returns></returns>
        private static int NextIndex(SlaveDatabaseOptionsWrap[] wraps)
        {
            int index = -1, total = 0;
            for (int i = 0; i < wraps.Length; i++)
            {
                SlaveDatabaseOptionsWrap wrap = wraps[i];
                wrap.Current += wrap.Weight;
                total += wrap.Weight;
                if (index == -1 || wraps[index].Current < wrap.Current)
                {
                    index = i;
                }
            }

            wraps[index].Current -= total;
            return index;
        }
        #endregion

        #endregion

        #region "私有类 从数据库选项包裹"
        /// <summary>
        /// 私有类 从数据库选项包裹
        /// </summary>
        private class SlaveDatabaseOptionsWrap : SlaveDatabaseInfo
        {
            #region "属性"
            #region "获取或设置 当前权重"
            /// <summary>
            /// 获取或设置 当前权重
            /// </summary>
            public int Current { get; set; }
            #endregion

            #endregion

            #region "构造函数"
            #region "初始化一个 <see cref="SlaveDatabaseOptionsWrap"/> 类型的新实例"
            /// <summary>
            /// 初始化一个 <see cref="SlaveDatabaseOptionsWrap"/> 类型的新实例
            /// </summary>
            /// <param name="slave">从数据库选项</param>
            public SlaveDatabaseOptionsWrap(SlaveDatabaseInfo slave)
            {
                Weight = slave.Weight;
            }
            #endregion

            #endregion

        }
        #endregion

    }
    #endregion

}
